package com.cavy.platform.client.domain;

import com.cavy.platform.client.BeansProvider;
import com.cavy.platform.client.CavyConfig;
import com.cavy.platform.client.message.ContainerExecutedFailMessage;
import com.cavy.platform.client.message.ContainerExecutedSuccessMessage;
import com.cavy.platform.client.message.Message;
import com.cavy.platform.client.service.DockerService;
import com.cavy.platform.client.service.WebSocketService;
import com.spotify.docker.client.messages.Event;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author 唐旭
 * @Date 2018/7/18
 */
@Data
@Slf4j
public class ContainerInfo implements ContainerRunnable, DockerEventHandler {
    public static final String CONTAINER_PATH = "/cavy/workdir/";
    public static final String SPLIT = "-";
    //镜像
    protected String image;
    //命令
    protected String command;
    //运行插件的user
    protected String user;
    //挂载的卷
    protected List<ContainerVolume> volumes = new ArrayList<>();
    //环境变量
    protected List<ContainerEnvironment> environments = new ArrayList<>();
    //工作目录
    protected String workDir;
    //插件
    protected ContainerPlugin plugin;
    //依赖
    protected List<StageDependency> dependencyList = new ArrayList<>();
    //是否允许失败
    protected boolean allowFailure = false;


    //容器id
    protected String dockerContainerId;
    //容器名称
    protected String containerTagName;
    //是否超时
    protected boolean timeOut;
    //退出状态码
    protected String exitCode;
    //是否中止
    protected boolean interrupt;
    //传递上下文给依赖容器
    protected ThreadLocal<ContainerRunnable> threadLocal = new ThreadLocal<>();
    //是否已经启动
    protected volatile boolean start = false;
    //是否允许运行
    protected volatile boolean run = true;

    /**
     * 启动步骤
     * 1,初始化 挂载(默认挂载一个工作目录)
     * 2,初始化 环境变量(加入job的信息)
     * 3,启动依赖容器
     * 4,启动主容器并等待停止
     * 5,依次销毁容器
     * 6,发送执行完成的消息
     *
     * @param parameter 运行参数
     * @param index     并行任务的索引
     * @throws ContainerRunningException 运行时发生的异常
     */
    @Override
    public void run(RunningParameter parameter, int index) throws ContainerRunningException, InterruptedException {
        initContainerTagName(parameter, index);
        //1,初始化 挂载(默认挂载一个工作目录)
        initVolumes(parameter);
        //2,初始化 环境变量(加入job的信息)
        initEnvironments(parameter, index);
        threadLocal.set(this);

        DockerService bean = BeansProvider.getBean(DockerService.class);
        assert bean != null;

        Iterator<ContainerRunCommand> commandList = initContainerRunCommandList(bean, parameter, this);


        Exception runException = null;
        try {
            while (run && commandList.hasNext()) {
                commandList.next().exec();
            }
        } catch (Exception e) {
            runException = e;
            throw new ContainerRunningException(e.getMessage());
        } finally {
            //移除容器
            removeContainer(bean);
            //发送执行完成的消息
            //根据容器退出代码生成消息,注意容器超时也需要发送消息
            Message message = buildMessage(runException, exitCode, parameter, index);
            WebSocketService webSocketService = BeansProvider.getBean(WebSocketService.class);
            assert webSocketService != null;
            webSocketService.send(message);
        }
    }

    protected Iterator<ContainerRunCommand> initContainerRunCommandList(DockerService bean, RunningParameter parameter, ContainerInfo containerInfo) {
        List<ContainerRunCommand> commands = new ArrayList<>();
        commands.add(new ContainerRunCommand() {
            @Override
            public void exec() throws InterruptedException, ContainerRunningException {
                //创建依赖容器
                //注册依赖容器停止事件处理器-->停止整个流程,调用移除命令
                //启动依赖容器
                //等待依赖容器启动成功
                int i = 0;
                for (StageDependency stageDependency : dependencyList) {
                    stageDependency.run(parameter, i);
                    i++;
                }
            }
        });
        commands.add(new ContainerRunCommand() {
            @Override
            public void exec() throws InterruptedException, ContainerRunningException {
                //创建主容器(如果主容器正在创建时,依赖容器停止了,则主容器创建会失败)
                bean.createContainer(containerInfo);
            }
        });
        commands.add(new ContainerRunCommand() {
            @Override
            public void exec() throws InterruptedException, ContainerRunningException {
                //注册容器结束事件处理器--> 停止依赖容器,调用移除命令
                bean.registerEventHandler(containerInfo);
            }
        });
        commands.add(new ContainerRunCommand() {
            @Override
            public void exec() throws InterruptedException, ContainerRunningException {
                //启动主容器
                bean.startContainer(dockerContainerId);
            }
        });
        commands.add(new ContainerRunCommand() {
            @Override
            public void exec() throws InterruptedException, ContainerRunningException {
                //等待主容器停止
                waitStopEvent();
            }
        });
        return commands.iterator();
    }

    private void removeContainer(DockerService bean) {
        try {
            bean.removeContainer(this);
            for (StageDependency item : dependencyList) {
                bean.removeContainer(item);
            }
        } catch (ContainerRunningException e) {
            log.error("移除容器发生错误,错误类型:{},错误信息:", e.getClass(), e.getMessage());
        }
    }


    protected void waitStopEvent() throws InterruptedException {
        synchronized (this) {
            while (StringUtils.isEmpty(exitCode) && run) {
                this.wait();
            }
        }
    }

    protected void waitStartEvent() throws InterruptedException {
        synchronized (this) {
            while (!start && run) {
                this.wait();
            }
        }
    }

    protected void initContainerTagName(RunningParameter parameter, int index) {
        containerTagName = parameter.getWorkspaceName() + SPLIT + parameter.getProjectName() + SPLIT +
                parameter.getPipelineName() + SPLIT + parameter.getNumber() + SPLIT + parameter.getStageName() + SPLIT + index;
    }

    /**
     * 初始化容器环境变量
     * 1,设置 工作空间名称,项目名称,流水线名称,步骤名称,index
     * 2,设置 工作目录变量
     *
     * @param parameter 运行参数
     * @param index     并行运行的索引
     */
    protected void initEnvironments(RunningParameter parameter, int index) {
        environments.add(new ContainerEnvironment(ContainerEnvironment.WORKSPACE_NAME, parameter.getWorkspaceName()));
        environments.add(new ContainerEnvironment(ContainerEnvironment.PROJECT_NAME, parameter.getProjectName()));
        environments.add(new ContainerEnvironment(ContainerEnvironment.PIPELINE_NAME, parameter.getPipelineName()));
        environments.add(new ContainerEnvironment(ContainerEnvironment.JOB_NUMBER, parameter.getNumber()));
        environments.add(new ContainerEnvironment(ContainerEnvironment.STAGE_NAME, parameter.getStageName()));
        environments.add(new ContainerEnvironment(ContainerEnvironment.INDEX, String.valueOf(index)));
        environments.add(new ContainerEnvironment(ContainerEnvironment.CAVY_DIR, CONTAINER_PATH));
    }

    /**
     * 初始化容器挂载
     * 1,默认挂载工作目录
     * 2,挂载用户目录
     *
     * @param parameter 运行参数
     */
    protected void initVolumes(RunningParameter parameter) {
        CavyConfig config = BeansProvider.getBean(CavyConfig.class);
        assert config != null;
        String baseDir = config.getBaseDir();
        String hostPath = getHostDir(baseDir, parameter);
        volumes.add(new ContainerVolume(hostPath, CONTAINER_PATH));
    }

    //获取容器路径 例如: baseDir/job-stage-index/
    private String getHostDir(String baseDir, RunningParameter parameter) {
        return baseDir + parameter.getWorkspaceName() + SPLIT + parameter.getProjectName() + SPLIT +
                parameter.getPipelineName() + SPLIT + parameter.getNumber() + File.separator;
    }

    @Override
    public void stop() throws InterruptedException, ContainerRunningException {
        stop(false, 0);
    }

    private Message buildMessage(Exception runException, String exitCode, RunningParameter parameter, int index) {
        if (interrupt) {
            return new ContainerExecutedFailMessage("任务被终止", exitCode, timeOut, parameter, index, this);
        }
        if (allowFailure) {
            return new ContainerExecutedSuccessMessage(parameter, this, index);
        }
        if (runException != null || !DockerService.SUCCESS_EXIT_CODE.equals(exitCode) || timeOut) {
            return new ContainerExecutedFailMessage(runException, exitCode, timeOut, parameter, index, this);
        }
        return new ContainerExecutedSuccessMessage(parameter, this, index);
    }

    /**
     * 停止容器运行
     * 如果以超时方式停止容器,并且容器不允许失败,则会抛出ContainerRunningException,并安全的结束容器运行
     * 在停止主容器后,bean.runContainerWaitStop(this);也将执行完成,然后会依次停止,并且开始释放资源和发送消息
     *
     * @param timeOut true:超时停止;false:未超时
     * @param index   并行运行的索引
     * @throws ContainerRunningException 容器超时,容器镜像运行错误等问题
     */
    public void stop(boolean timeOut, int index) throws ContainerRunningException {
        synchronized (this) {
            //停止依赖容器
            for (StageDependency stage : dependencyList) {
                stage.stop(timeOut, index);
            }
            boolean notify = run;
            run = false;
            //不能多次调用notify
            if (notify) {
                this.notifyAll();
            }
            interrupt = true;
        }
    }

    public List<String> toEnv() {
        List<String> result = new ArrayList<>();
        for (ContainerEnvironment environment : environments) {
            result.add(environment.toEnvString());
        }
        return result;
    }

    public String getContainerTagName() {
        return containerTagName;
    }

    @Override
    public void dieEvent(Event event) {
        //获取exitCode
        exitCode = event.actor().attributes().get("exitCode");
        synchronized (this) {
            this.notifyAll();
        }
    }

    public boolean isSuccess() {
        if (allowFailure) {
            return true;
        }
        return DockerService.SUCCESS_EXIT_CODE.equals(exitCode);
    }
}
